package com.ruoyi.common.filter;

import com.ruoyi.common.annotation.SMConvert;
import com.ruoyi.common.enums.SMConvertType;
import com.ruoyi.common.exception.GlobalException;
import com.ruoyi.common.utils.SM4Utils;
import com.ruoyi.common.utils.SignatureUtils;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import static com.ruoyi.common.enums.SMConvertType.SM4_AND_SIGNATURE;

/**
 * 解密拦截器
 * 加密：原始数据 -> 加密 -> 密文
 * 签名：密文 -> 签名 -> 签名值
 * @author zpp
 */
@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
@Component
public class SM4DecryptInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // todo 根据控制规则判断是否开启

        Object result = invocation.proceed();
        // 解密 及 验签
        try {
            if (result instanceof List) {
                List<?> list = (List<?>) result;
                for (Object item : list) {
                    decryptIfNeeded(item);
                }
            } else {
                decryptIfNeeded(result);
            }
        } catch (Exception e) {
            log.info(e.getMessage());
            throw new GlobalException("数据疑似被篡改");
            // 为避免用户混淆 返回空值
            // return new ArrayList<>();
        }
        return result;
    }

    private void decryptIfNeeded(Object obj) throws Exception{

        if (obj == null) {
            return;
        }

        Field[] fields = obj.getClass().getDeclaredFields();
        StringBuilder buffer = new StringBuilder();

        for (Field field : fields) {
            field.setAccessible(true);
            SMConvert annotation = field.getAnnotation(SMConvert.class);
            if (annotation == null) {
                continue;
            }

            SMConvertType convertType = annotation.value();
            switch (convertType) {
                case SM4:
                    field.set(obj, SM4Utils.decrypt((String) field.get(obj)));
                    break;
                case SIGNATURE:
                case SM4_AND_SIGNATURE:
                    // 先取签名值 再 解密
                    buffer.append(field.get(obj)).append("&");

                    if (convertType == SM4_AND_SIGNATURE) {
                        field.set(obj, SM4Utils.decrypt((String) field.get(obj)));
                    }
                    break;
            }

        }

        // 开始验签
        if (buffer.length() > 0) {
            verifySignature(obj, buffer.toString());
        }
    }

    private void verifySignature(Object obj, String dataToVerify) throws Exception{
        Field signatureField = obj.getClass().getDeclaredField("signature");
        if (signatureField != null) {
            signatureField.setAccessible(true);
            String signature = (String) signatureField.get(obj);
            if (StringUtils.isBlank(signature)) {
                throw new RuntimeException("签名值为空");
            }
            if (!SignatureUtils.verifySignature(dataToVerify, signature)) {
                throw new RuntimeException("验签失败！数据疑似被篡改!!!");
            }
        }
    }

    // 加载插件
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}
